#!/usr/bin/env python3
"""
Git Diff Analyzer - A Python script to check changes in the current repository and output user-friendly diff
"""

import subprocess
import sys
import os
import argparse
from pathlib import Path
import json


def run_git_command(cmd):
    """Run a git command and return the output"""
    try:
        result = subprocess.run(
            cmd,
            shell=True,
            check=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
        return result.stdout.strip()
    except subprocess.CalledProcessError as e:
        print(f"Error running git command: {cmd}")
        print(f"Error message: {e.stderr}")
        return None


def get_git_status():
    """Get the git status of the current repository"""
    status_output = run_git_command("git status --porcelain")
    if status_output is None:
        return []

    # Parse the status output
    changes = []
    for line in status_output.split("\n"):
        if line.strip():
            status_code = line[:2].strip()
            filename = line[3:].strip()

            # Determine the change type
            if status_code == "A":  # Added
                change_type = "Added"
            elif status_code == "M":  # Modified
                change_type = "Modified"
            elif status_code == "D":  # Deleted
                change_type = "Deleted"
            elif status_code == "R":  # Renamed
                change_type = "Renamed"
            elif status_code == "C":  # Copied
                change_type = "Copied"
            elif status_code.startswith("??"):  # Untracked
                change_type = "Untracked"
            elif status_code.startswith("!!"):  # Ignored
                change_type = "Ignored"
            else:
                change_type = f"Changed ({status_code})"

            changes.append(
                {"type": change_type, "filename": filename, "status_code": status_code}
            )

    return changes


def get_diff_for_file(filename, status_code="M"):
    """Get the diff for a specific file"""
    try:
        if status_code.startswith("??"):  # Untracked file
            # For untracked files, show the content
            try:
                with open(filename, "r", encoding="utf-8") as f:
                    content = f.read()
                if content:
                    return f"New file content ({len(content)} chars):\n{content[:1000]}"  # Limit content
                return "Empty file"
            except Exception:
                return "Binary file or cannot read"
        else:
            # For tracked files, show the diff
            result = subprocess.run(
                ["git", "diff", "--", filename],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
            )

            if result.returncode == 0 and result.stdout:
                return result.stdout
            elif result.returncode != 0 and "fatal:" in result.stderr:
                # If normal diff fails, try diff with cached
                result = subprocess.run(
                    ["git", "diff", "--cached", "--", filename],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                )
                if result.returncode == 0:
                    return result.stdout

            return "No changes detected or file is binary"
    except Exception as e:
        return f"Error getting diff: {str(e)}"


def get_summary():
    """Get a summary of changes in the repository"""
    # Get number of commits ahead/behind from remote
    try:
        ahead_behind = run_git_command(
            "git rev-list --left-right --count HEAD...origin/$(git branch --show-current)"
        )
        if ahead_behind:
            ahead, behind = ahead_behind.split()
            ahead = int(ahead)
            behind = int(behind)
        else:
            ahead, behind = 0, 0
    except:
        ahead, behind = 0, 0

    # Get branch name
    branch_name = run_git_command("git branch --show-current")
    if not branch_name:
        branch_name = "unknown"

    # Get last commit info
    last_commit = run_git_command("git log -1 --pretty=format:'%h - %an, %ar : %s'")

    return {
        "branch": branch_name,
        "ahead": ahead,
        "behind": behind,
        "last_commit": last_commit,
    }


def format_diff(changes, show_content=True):
    """Format the changes in a user-friendly way"""
    if not changes:
        print("✅ No changes detected in the repository.")
        return

    print(f"\n📋 Changes Summary:")
    print(f"{'=' * 50}")

    # Group changes by type
    added = [c for c in changes if c["type"] == "Added"]
    modified = [c for c in changes if c["type"] == "Modified"]
    deleted = [c for c in changes if c["type"] == "Deleted"]
    untracked = [c for c in changes if c["type"] == "Untracked"]
    renamed = [c for c in changes if c["type"] == "Renamed"]
    other = [
        c
        for c in changes
        if c["type"] not in ["Added", "Modified", "Deleted", "Untracked", "Renamed"]
    ]

    if added:
        print(f"\n➕ {len(added)} file(s) added:")
        for change in added:
            print(f"   - {change['filename']}")

    if modified:
        print(f"\n📝 {len(modified)} file(s) modified:")
        for change in modified:
            print(f"   - {change['filename']}")

    if deleted:
        print(f"\n🗑️ {len(deleted)} file(s) deleted:")
        for change in deleted:
            print(f"   - {change['filename']}")

    if untracked:
        print(f"\n🆕 {len(untracked)} untracked file(s):")
        for change in untracked:
            print(f"   - {change['filename']}")

    if renamed:
        print(f"\n🔄 {len(renamed)} file(s) renamed:")
        for change in renamed:
            print(f"   - {change['filename']}")

    if other:
        print(f"\n⚡ Other changes:")
        for change in other:
            print(f"   - {change['type']}: {change['filename']}")

    # Show detailed diff for each file if requested
    if show_content:
        print(f"\n🔍 Detailed Changes:")
        print(f"{'=' * 50}")

        for change in changes:
            print(f"\n📄 {change['type']}: {change['filename']}")
            print("-" * 30)

            # Get diff for the file
            diff = get_diff_for_file(change["filename"], change["status_code"])
            if diff:
                print(diff)
            else:
                print("No diff available for this file")
            print()


def get_full_diff_content(changes):
    """Get the full diff content for all changed files"""
    full_diff = []

    for change in changes:
        filename = change["filename"]
        status_code = change["status_code"]

        # Get diff for the file
        diff_content = get_diff_for_file(filename, status_code)
        if diff_content:
            full_diff.append(f"File: {filename} ({change['type']})\n{diff_content}")

    return "\n".join(full_diff)


def generate_ai_summary(changes, full_diff_content):
    """Generate an AI summary of the changes for commit message"""
    try:
        # Try to import required packages for AI functionality
        import os
        from langchain_core.prompts import ChatPromptTemplate
        from langchain_core.output_parsers import StrOutputParser
        from langchain_core.runnables import Runnable
        from dotenv import load_dotenv
        from pathlib import Path
        import getpass

        dotenv_path = Path("gitee_config.env")
        load_dotenv(dotenv_path=dotenv_path)

        # Initialize LangSmith tracking if enabled
        langsmith_enabled = os.getenv("LANGCHAIN_API_KEY") is not None
        if langsmith_enabled:
            os.environ["LANGCHAIN_TRACING_V2"] = "true"
            project_name = os.getenv("LANGCHAIN_PROJECT", "git-diff-analyzer")
            os.environ["LANGCHAIN_PROJECT"] = project_name

            print(f"🔄 LangSmith tracing enabled for project: {project_name}")

        # Determine which LLM to use based on availability
        llm = None
        model_name = None

        # Try custom OpenAI-compatible provider first
        custom_base_url = os.getenv("CUSTOM_BASE_URL")
        custom_api_key = os.getenv("CUSTOM_API_KEY")
        custom_model = os.getenv("CUSTOM_MODEL")

        if custom_base_url and custom_api_key and custom_model:
            try:
                from langchain_openai import ChatOpenAI

                llm = ChatOpenAI(
                    base_url=custom_base_url,
                    api_key=custom_api_key,
                    model=custom_model,
                    temperature=0.3,
                )
                model_name = f"Custom ({custom_model}) at {custom_base_url}"
            except ImportError:
                print("⚠️  LangChain OpenAI not available, skipping custom provider")
                pass

        # Try OpenAI next
        if llm is None:
            try:
                from langchain_openai import ChatOpenAI

                api_key = os.getenv("OPENAI_API_KEY")
                if api_key:
                    llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3)
                    model_name = "OpenAI GPT-3.5-turbo"
            except ImportError:
                pass

        # Try Anthropic if OpenAI is not available
        if llm is None:
            try:
                from langchain_anthropic import ChatAnthropic

                api_key = os.getenv("ANTHROPIC_API_KEY")
                if api_key:
                    llm = ChatAnthropic(
                        model="claude-3-haiku-20240307", temperature=0.3
                    )
                    model_name = "Anthropic Claude-3-Haiku"
            except ImportError:
                pass

        # Try Ollama as fallback
        if llm is None:
            try:
                from langchain_ollama import ChatOllama

                llm = ChatOllama(model="llama2", temperature=0.3)
                model_name = "Ollama Llama2"
            except ImportError:
                pass

        if llm is None:
            print(
                "❌ No AI model available. Please install one of the following packages:"
            )
            print("   - langchain-openai (pip install langchain-openai)")
            print("   - langchain-anthropic (pip install langchain-anthropic)")
            print("   - langchain-ollama (pip install langchain-ollama)")
            print("\nOr set up the appropriate API keys for OpenAI or Anthropic.")
            print(
                "\nFor custom OpenAI-compatible providers, set these environment variables:"
            )
            print("   - CUSTOM_BASE_URL (your API base URL)")
            print("   - CUSTOM_API_KEY (your API key)")
            print("   - CUSTOM_MODEL (your model name)")
            return None

        print(f"🤖 Using AI model: {model_name}")

        # Create a prompt for generating commit messages
        # Use .format() with positional arguments to avoid f-string issues with diff content
        user_message = f"""
Analyze the following Git diff and generate an appropriate commit message:

{full_diff_content.replace("{", "{{").replace("}", "}}")}

Based on these changes, provide a Git commit message that:
1. Starts with a short summary (50 characters or less)
2. Uses imperative mood (e.g., 'Add', 'Fix', 'Update', 'Remove')
3. Is followed by a more detailed description if needed
4. Focuses on what changed and why

Return just the commit message.
            """

        prompt = ChatPromptTemplate.from_messages(
            [
                (
                    "system",
                    "You are an expert software developer assistant. Your task is to analyze Git diffs and generate a concise, clear, and informative Git commit message that follows conventional commit format when appropriate. Focus on what changed and why, not how. The commit message should be in imperative mood and should summarize the changes in 50 characters or less for the title, followed by a more detailed description if needed.",
                ),
                (
                    "user",
                    user_message,
                ),
            ]
        )

        # Chain the prompt, LLM, and output parser
        chain: Runnable = prompt | llm | StrOutputParser()

        # Generate the summary
        print("🔄 Generating AI summary of changes...")
        result = chain.invoke({})

        return result.strip()

    except ImportError as e:
        print(f"❌ Required package not installed: {e}")
        print("To use AI summary, install LangChain and an LLM provider:")
        print("  - For OpenAI: pip install langchain-openai openai")
        print("  - For Anthropic: pip install langchain-anthropic anthropic")
        print("  - For Ollama: pip install langchain-ollama ollama")
        print(
            "  - For custom OpenAI-compatible providers: pip install langchain-openai"
        )
        print(
            "\nFor custom OpenAI-compatible providers, set these environment variables:"
        )
        print("   - CUSTOM_BASE_URL (your API base URL)")
        print("   - CUSTOM_API_KEY (your API key)")
        print("   - CUSTOM_MODEL (your model name)")
        return None
    except Exception as e:
        print(f"❌ Error generating AI summary: {e}")
        return None


def main():
    parser = argparse.ArgumentParser(
        description="Check changes in the current repository and output user-friendly diff",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s                    # Show all changes in user-friendly format
  %(prog)s --no-content       # Show only change types without detailed content
  %(prog)s --summary          # Show only repository summary
  %(prog)s --ai-summary       # Generate AI summary for commit message

Environment Variables:
  For AI functionality, you can use:
  - OPENAI_API_KEY, ANTHROPIC_API_KEY for respective services
  - CUSTOM_BASE_URL, CUSTOM_API_KEY, CUSTOM_MODEL for custom OpenAI-compatible providers
  - LANGCHAIN_API_KEY, LANGCHAIN_PROJECT for LangSmith tracking
        """,
    )

    parser.add_argument(
        "--no-content",
        action="store_true",
        help="Show change types only, without detailed content",
    )

    parser.add_argument(
        "--summary", action="store_true", help="Show only repository summary"
    )

    parser.add_argument(
        "--ai-summary",
        action="store_true",
        help="Generate AI summary of changes for commit message with LangSmith support. Supports custom OpenAI-compatible providers via CUSTOM_BASE_URL, CUSTOM_API_KEY, CUSTOM_MODEL environment variables",
    )

    args = parser.parse_args()

    # Check if we're in a git repository
    repo_path = run_git_command("git rev-parse --git-dir")
    if not repo_path:
        print("❌ This is not a git repository or git is not available.")
        sys.exit(1)

    # Get repository summary
    summary = get_summary()

    print(f"\n📊 Repository Summary:")
    print(f"{'=' * 50}")
    print(f"Branch: {summary['branch']}")
    print(f"Commits ahead of remote: {summary['ahead']}")
    print(f"Commits behind remote: {summary['behind']}")
    print(f"Last commit: {summary['last_commit']}")

    if args.summary:
        return

    # Get git status
    changes = get_git_status()

    # If AI summary is requested, generate and print it
    if args.ai_summary:
        if not changes:
            print("✅ No changes detected. Nothing to summarize.")
            return

        print(f"\n🔍 Getting detailed changes for AI analysis...")
        full_diff_content = get_full_diff_content(changes)

        if not full_diff_content.strip():
            print("⚠️  No diff content available for AI analysis.")
            return

        ai_summary = generate_ai_summary(changes, full_diff_content)

        if ai_summary:
            print(f"\n📝 AI-Generated Commit Message:")
            print(f"{'=' * 50}")
            print(ai_summary)
            print(f"{'=' * 50}")
            print("\n💡 You can use this message for your git commit:")
            print(f'   git commit -m "{ai_summary.split(chr(10))[0]}"')
        else:
            print("❌ Could not generate AI summary.")
            # Still show regular output if AI fails
            format_diff(changes, show_content=not args.no_content)
    else:
        # Format and display the changes normally
        format_diff(changes, show_content=not args.no_content)


if __name__ == "__main__":
    main()
